iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

初階 Rails 工程師的養成系列 第 10

Day10. 深入瞭解 Block - Block Part3

  • 分享至 

  • xImage
  •  

在Block系列文章裡面

  • Day8 提到了Block, Proc
  • Day9 提到了yield
  • Day10 開始詳述Proc物件,以及了解 yield背後的操作原理。

Proc 一共分為proclambda 兩種物件。兩者功能大致上相同,不過因些微的不同而導致了極大的差別。個人推薦使用lambda,因為其行為與一般的方法差別不大,不過以下會帶過Proc, Lambda之間的用法,畢竟雖然平常用不到,但面試卻很常被問到。

Proc

首先我們列出Proc的特性

  • 使用 Proc.new {} , proc {} 定義
  • 對於參數的引入沒有嚴格定義
proc = Proc.new { |x| x }
#=> #<Proc:0x00007fd4de0c8248@(irb):8>

proc.call(1)
#=> 1

proc.call
#=> nil
  • proc 裡頭寫 return 就不會往下走了
# 沒有return
def apple_machine(adjective)
  proc = Proc.new { |x| x }
  
  [proc[adjective], 'apple'].compact.join('-')
end  
    
apple_machine('beautiful')  #=> 'beautiful-apple'    
# 有return
def apple_machine(adjective)
  proc = Proc.new { |x| return x }
  
  [proc[adjective], 'apple'].compact.join('-')
end  
    
apple_machine('beautiful')  #=> 'beautiful'

Lambda

  • 使用 -> {} , lambda {}定義
  • 對於參數的引入有嚴格定義
proc = -> (x){ x }
#=> #<Proc:0x00007fd4de10a580@(irb):18 (lambda)>

proc.call(1)
#=> 1

proc.call
#=> ArgumentError (wrong number of arguments (given 0, expected 1))
  • lambda 裡頭寫 return 就會繼續往下走
# 有return
def apple_machine(adjective)
  proc = -> (x){ return x }
  
  [proc[adjective], 'apple'].compact.join('-')
end  
    
apple_machine('beautiful')  #=> 'beautiful-apple'  

Proc call

Day8 提過了基本的用法,這邊直接從實例開始介紹。未來學會proc方法之後,若包成方法會導致增加程式碼的易讀性跟不必要性,或者只是懶得包方法的話,就可以使用呼叫Proc的方式,宣告一個匿名函式,在同一個方法裡面使用。

若收到類別為陣列,將所有的元素前方加入井字號,並將陣列裡的元素全部用-連起,並且在每一個元素前面+個#字號印出來。

displayed_record = -> (opcode) { "##{opcode}" }

at = -> (opcodes) do
  return displayed_record.call(opcodes) unless opcodes.is_a?(Array) 
  
  opcodes.map { |opcode| displayed_record.call(opcode) }.join('-')
end

# 使用方式

at.call(%w[50 51 54])             #=> "#50-#51-#54"
at.call('8000')                   #=> "#8000"

Proc.call sugar

Day8 已經提到Proc.call的四個語法糖,個人喜好的是第二種的.()的寫法。漢漢老師內心總是想著,這種用法可以讓沒看過的人滿頭問號,若更偏激的軟體工程師甚至可能把.改掉,這樣一來可以跟看到有人改壞我的code了。

不過實際上這件事情並沒有真正的發生過,以上都只是自己純粹的想像跟腦補而已。

it_day10 = -> { puts "IT鐵人賽第10天" }

it_day10.call
it_day10.()
it_day10[]
it_day10.===

What is & in Ruby

&與在Ruby的程式語言中,和C++的不太一樣。Ruby& 可以將 proc(or lambda) 轉為 block 使用

# 將 proc 轉為 block
my_proc = Proc.new { |x| x + 2 }
[1, 2, 3].map(&my_proc)
=> [3, 4, 5]

# 將 proc 轉為 block
[1, 2, 3].map(&->(x) { x + 2})
=> [3, 4, 5]

&也可以將 block 轉為proc 使用

def it_day9(&it_block)
  it_block.call
end

# 直接使用 proc 
it_day9(&->{ 'foo' })
# 將 block 轉為 proc 使用
it_day9 { 'foo' }

⭐️ & 搭配 symbol 會自動將symbol 轉為 to_proc的用法。這邊可能比較難理解,重點只要記住,&搭配符號,代表這個符號有proc的功能,因此可以to_proc,而這些可以to_proc的方法通常都是Ruby/ Rails 提供的方法。

# ruby 的 to_i, to_f, to_h 就是轉型態的方法,同樣的 to_proc 就是轉為proc(or lambda)
:+.to_proc.call(1,1)         # 1+1
:upcase.to_proc.call('han')  # 'han'.upcase

# 上面的意思等同於下面的意思
[[1,1], [2,2], [3,3]].map(&:sum)
#=> [2, 4, 6]

[[1,1], [2,2], [3,3]].map {|arr| arr.sum}
#=> [2, 4, 6]

yield & Implicit Block

今天的另外一個主角yield

Day9 我們提到了所有跟yield的用法,如果只要會用法的話,看 Day9 就夠了!今天會從Proc的角度來介紹blockblock專有名詞稱為Explicit Blocks,使用了&block name來做表示。

以下一共有兩種方式可以將block帶入使用

def it_day10(&it_block)
  it_block.call
end

# 一共有兩種方式
it_day10(&->{ 'foo' })
it_day10 { 'foo' }

我們可以用 yield 改寫成下列的格式。yield表示法有個專有名詞稱為Implicit Blockyieldblock.call的簡寫,可以不引入參數,也可以引入參數。

# yield without argument
def it_day10
  yield
end

it_day10(&->{ 'foo' })
it_day10 { 'foo' }

然後,不能寫return

# yield with argument
def it_day10_arg(x = 1)
  y = yield x
  
  y + 1
end

it_day10_arg(2, &->(x) { x + 1 }) #=> 4
it_day10_arg(2) { |x| x + 1 }     #=> 4

it_day10_arg(2) { |x| return x + 1 } #=> LocalJumpError (unexpected return)

yield 還可以搭配 block_given? 使用,可以區別block和沒有block的使用方法

def it_day10
  block_given? ? yield : 'bar'
end

it_day10(&->{ 'foo' })  #=> 'foo'
it_day10                #=> 'bar'

結論

block差不多到這邊就講解完了,後續會介紹class 的用法。

參考資料


上一篇
Day9. functional programming in Ruby - Block Part2
下一篇
Day11. 活用 Ruby Class
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言